{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b518b04cbfe0"
      },
      "source": [
        "##### Copyright 2020 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "906e07f6e562"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "da6087fbd570"
      },
      "source": [
        "# 기능적 API"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "d169f4a559d5"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/guide/keras/functional\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/guide/keras/functional.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/guide/keras/functional.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub에서 소스보기</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/guide/keras/functional.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">노트북 다운로드</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8d4ac441b1fc"
      },
      "source": [
        "## 설정"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ec52be14e686"
      },
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "import tensorflow as tf\n",
        "from tensorflow import keras\n",
        "from tensorflow.keras import layers"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "871fbb54ea07"
      },
      "source": [
        "## 시작하기\n",
        "\n",
        "Keras *함수형 API*는 `tf.keras.Sequential` API보다 더 유연한 모델을 생성하는 방법입니다. 함수형 API는 비선형 토폴로지, 공유 레이어, 심지어 여러 입력 또는 출력이 있는 모델을 처리할 수 있습니다.\n",
        "\n",
        "주요 개념은 딥 러닝 모델은 일반적으로 레이어의 DAG(directed acyclic graph)라는 것입니다. 따라서 함수형 API는 *레이어의 그래프*를 빌드하는 방법입니다.\n",
        "\n",
        "다음 모델을 고려하십시오.\n",
        "\n",
        "```\n",
        "(input: 784-dimensional vectors)\n",
        "       ↧\n",
        "[Dense (64 units, relu activation)]\n",
        "       ↧\n",
        "[Dense (64 units, relu activation)]\n",
        "       ↧\n",
        "[Dense (10 units, softmax activation)]\n",
        "       ↧\n",
        "(output: logits of a probability distribution over 10 classes)\n",
        "```\n",
        "\n",
        "이 모델은 세 개의 레이어가 있는 기본 그래프입니다. 함수형 API를 사용하여 이 모델을 빌드하려면 먼저 입력 노드를 작성하세요."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8d477c91955a"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input(shape=(784,))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "13c14d993620"
      },
      "source": [
        "데이터의 모양은 784 차원 벡터로 설정됩니다. 각 샘플의 모양 만 지정되므로 배치 크기는 항상 생략됩니다.\n",
        "\n",
        "예를 들어 `(32, 32, 3)` 모양의 이미지 입력이있는 경우 다음을 사용합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "e4732e8e279b"
      },
      "outputs": [],
      "source": [
        "# Just for demonstration purposes.\n",
        "img_inputs = keras.Input(shape=(32, 32, 3))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "971bf8b5588f"
      },
      "source": [
        "리턴되는 `inputs` 에는 모델에 공급하는 입력 데이터의 모양 및 `dtype` 에 대한 정보가 포함됩니다. 모양은 다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ee96c179846a"
      },
      "outputs": [],
      "source": [
        "inputs.shape"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "866eee86d63e"
      },
      "source": [
        "dtype은 다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "480be92067f3"
      },
      "outputs": [],
      "source": [
        "inputs.dtype"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6c93172cdfba"
      },
      "source": [
        "이 `inputs` 객체에서 레이어를 호출하여 레이어 그래프에서 새 노드를 만듭니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "b50da8b1c28d"
      },
      "outputs": [],
      "source": [
        "dense = layers.Dense(64, activation=\"relu\")\n",
        "x = dense(inputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0f36afe42ff3"
      },
      "source": [
        "\"레이어 호출\" 동작은 \"입력\"에서 생성된 레이어로 화살표를 그리는 것과 같습니다. 입력을 `dense` 레이어로 \"전달\"하고 `x`를 출력으로 가져옵니다.\n",
        "\n",
        "레이어 그래프에 레이어를 몇 개 레이어를 더 추가해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "463d5cd0c484"
      },
      "outputs": [],
      "source": [
        "x = layers.Dense(64, activation=\"relu\")(x)\n",
        "outputs = layers.Dense(10)(x)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e379f089b044"
      },
      "source": [
        "이 시점에서 레이어 그래프에서 입력 및 출력을 지정하여 `Model`을 작성할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7820cc2209a6"
      },
      "outputs": [],
      "source": [
        "model = keras.Model(inputs=inputs, outputs=outputs, name=\"mnist_model\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "c9aa111852d3"
      },
      "source": [
        "모델 요약이 어떻게 보이는지 확인하십시오."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4949ab8242e8"
      },
      "outputs": [],
      "source": [
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "99ab8535d6c3"
      },
      "source": [
        "모델을 그래프로 플롯 할 수도 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "6872f1b1b8b8"
      },
      "outputs": [],
      "source": [
        "keras.utils.plot_model(model, \"my_first_model.png\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6d9880136879"
      },
      "source": [
        "그리고 선택적으로 플롯 된 그래프에 각 레이어의 입력 및 출력 모양을 표시합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "aa14046d3388"
      },
      "outputs": [],
      "source": [
        "keras.utils.plot_model(model, \"my_first_model_with_shape_info.png\", show_shapes=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "71969f9c91bb"
      },
      "source": [
        "이 그림과 코드는 거의 동일합니다. 코드 버전에서 연결 화살표는 호출 작업으로 대체됩니다.\n",
        "\n",
        "\"레이어 그래프\"는 딥 러닝 모델을위한 직관적 인 정신 이미지이며 함수형 API는이를 밀접하게 반영하는 모델을 만드는 방법입니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "775b997c8c28"
      },
      "source": [
        "## 교육, 평가 및 추론\n",
        "\n",
        "훈련, 평가 및 추론은 `Sequential` 모델과 같이 함수형 API를 사용하여 빌드된 모델에 대해 같은 방식으로 작동합니다.\n",
        "\n",
        "`Model` 클래스는 내장 훈련 루프(`fit()` 메서드)와 내장 평가 루프(`evaluate()` 메서드)를 제공합니다. [이러한 루프를 쉽게 사용자 정의](https://www.tensorflow.org/guide/keras/customizing_what_happens_in_fit/)하여 지도 학습(예: [GANs](/examples/generative/dcgan_overriding_train_step/)) 이상의 학습 루틴을 구현할 수 있습니다.\n",
        "\n",
        "여기에서 MNIST 이미지 데이터를로드하고 벡터로 재구성하고 데이터에 모델을 맞추고 (유효성 분할에서 성능을 모니터링하는 동안) 테스트 데이터에서 모델을 평가하십시오."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "e61366d54487"
      },
      "outputs": [],
      "source": [
        "(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()\n",
        "\n",
        "x_train = x_train.reshape(60000, 784).astype(\"float32\") / 255\n",
        "x_test = x_test.reshape(10000, 784).astype(\"float32\") / 255\n",
        "\n",
        "model.compile(\n",
        "    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
        "    optimizer=keras.optimizers.RMSprop(),\n",
        "    metrics=[\"accuracy\"],\n",
        ")\n",
        "\n",
        "history = model.fit(x_train, y_train, batch_size=64, epochs=2, validation_split=0.2)\n",
        "\n",
        "test_scores = model.evaluate(x_test, y_test, verbose=2)\n",
        "print(\"Test loss:\", test_scores[0])\n",
        "print(\"Test accuracy:\", test_scores[1])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2e13d7168c86"
      },
      "source": [
        "자세한 내용은 [훈련 및 평가](https://www.tensorflow.org/guide/keras/train_and_evaluate/) 가이드를 참조하세요."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "26991ef4dbbb"
      },
      "source": [
        "## 저장 및 직렬화\n",
        "\n",
        "모델 저장 및 직렬화는 `Sequential` 모델과 같이 함수형 API를 사용하여 빌드된 모델에 대해 같은 방식으로 작동합니다. 함수형 모델을 저장하는 표준 방법은 `model.save()`를 호출하여 전체 모델을 단일 파일로 저장하는 것입니다. 모델을 빌드한 코드를 더 이상 사용할 수 없는 경우에도 나중에 이 파일에서 같은 모델을 다시 작성할 수 있습니다.\n",
        "\n",
        "저장된 이 파일에는 다음이 포함됩니다.\n",
        "\n",
        "- 모델 아키텍처\n",
        "- 모델 중량 값 (훈련 중 학습 된 값)\n",
        "- 모델 훈련 구성(있는 경우, `compile`로 전달)\n",
        "- 옵티마이저 및 상태(있는 경우, 중단한 곳에서 훈련을 다시 시작)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7e5e48669225"
      },
      "outputs": [],
      "source": [
        "model.save(\"path_to_my_model\")\n",
        "del model\n",
        "# Recreate the exact same model purely from the file:\n",
        "model = keras.models.load_model(\"path_to_my_model\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cfe2a761139b"
      },
      "source": [
        "자세한 내용은 모델 [직렬화 및 저장](https://www.tensorflow.org/guide/keras/save_and_serialize/) 가이드를 참조하세요."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b747517364a9"
      },
      "source": [
        "## 같은 레이어 그래프를 사용하여 여러 모델 정의하기\n",
        "\n",
        "함수형 API에서 모델은 레이어 그래프에 입력 및 출력을 지정하여 생성됩니다. 즉, 단일 레이어 그래프를 사용하여 여러 모델을 생성할 수 있습니다.\n",
        "\n",
        "아래 예에서는 같은 레이어 스택을 사용하여 두 모델을 인스턴스화합니다. 이미지 입력을 16차원 벡터로 변환하는 `encoder` 모델과 훈련을 위한 엔드 투 엔드 `autoencoder` 모델입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "f9924d8c9ed3"
      },
      "outputs": [],
      "source": [
        "encoder_input = keras.Input(shape=(28, 28, 1), name=\"img\")\n",
        "x = layers.Conv2D(16, 3, activation=\"relu\")(encoder_input)\n",
        "x = layers.Conv2D(32, 3, activation=\"relu\")(x)\n",
        "x = layers.MaxPooling2D(3)(x)\n",
        "x = layers.Conv2D(32, 3, activation=\"relu\")(x)\n",
        "x = layers.Conv2D(16, 3, activation=\"relu\")(x)\n",
        "encoder_output = layers.GlobalMaxPooling2D()(x)\n",
        "\n",
        "encoder = keras.Model(encoder_input, encoder_output, name=\"encoder\")\n",
        "encoder.summary()\n",
        "\n",
        "x = layers.Reshape((4, 4, 1))(encoder_output)\n",
        "x = layers.Conv2DTranspose(16, 3, activation=\"relu\")(x)\n",
        "x = layers.Conv2DTranspose(32, 3, activation=\"relu\")(x)\n",
        "x = layers.UpSampling2D(3)(x)\n",
        "x = layers.Conv2DTranspose(16, 3, activation=\"relu\")(x)\n",
        "decoder_output = layers.Conv2DTranspose(1, 3, activation=\"relu\")(x)\n",
        "\n",
        "autoencoder = keras.Model(encoder_input, decoder_output, name=\"autoencoder\")\n",
        "autoencoder.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e87d4185b652"
      },
      "source": [
        "여기서, 디코딩 아키텍처는 인코딩 아키텍처와 완전하게 대칭이므로 출력 형상은 입력 형상 `(28, 28, 1)`과 같습니다.\n",
        "\n",
        "(A)의 반대 `Conv2D` 층은 인 `Conv2DTranspose` 층, 및의 역 `MaxPooling2D` 층은 인 `UpSampling2D` 층."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9c746c1a0b79"
      },
      "source": [
        "## 레이어와 마찬가지로, 모든 모델은 callable입니다\n",
        "\n",
        "`Input` 또는 또 다른 레이어의 출력에서 모델을 호출함으로써 모델을 마치 레이어와 같이 취급할 수 있습니다. 모델을 호출함으로써 모델의 아키텍처를 재사용할 뿐만 아니라 가중치도 재사용합니다.\n",
        "\n",
        "실례를 위해, 다음은 인코더 모델과 디코더 모델을 만들고 두 번의 호출로 연결하여 자동 인코더 모델을 얻는 자동 인코더 예제에 대한 또 다른 설명입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "862ac58e928b"
      },
      "outputs": [],
      "source": [
        "encoder_input = keras.Input(shape=(28, 28, 1), name=\"original_img\")\n",
        "x = layers.Conv2D(16, 3, activation=\"relu\")(encoder_input)\n",
        "x = layers.Conv2D(32, 3, activation=\"relu\")(x)\n",
        "x = layers.MaxPooling2D(3)(x)\n",
        "x = layers.Conv2D(32, 3, activation=\"relu\")(x)\n",
        "x = layers.Conv2D(16, 3, activation=\"relu\")(x)\n",
        "encoder_output = layers.GlobalMaxPooling2D()(x)\n",
        "\n",
        "encoder = keras.Model(encoder_input, encoder_output, name=\"encoder\")\n",
        "encoder.summary()\n",
        "\n",
        "decoder_input = keras.Input(shape=(16,), name=\"encoded_img\")\n",
        "x = layers.Reshape((4, 4, 1))(decoder_input)\n",
        "x = layers.Conv2DTranspose(16, 3, activation=\"relu\")(x)\n",
        "x = layers.Conv2DTranspose(32, 3, activation=\"relu\")(x)\n",
        "x = layers.UpSampling2D(3)(x)\n",
        "x = layers.Conv2DTranspose(16, 3, activation=\"relu\")(x)\n",
        "decoder_output = layers.Conv2DTranspose(1, 3, activation=\"relu\")(x)\n",
        "\n",
        "decoder = keras.Model(decoder_input, decoder_output, name=\"decoder\")\n",
        "decoder.summary()\n",
        "\n",
        "autoencoder_input = keras.Input(shape=(28, 28, 1), name=\"img\")\n",
        "encoded_img = encoder(autoencoder_input)\n",
        "decoded_img = decoder(encoded_img)\n",
        "autoencoder = keras.Model(autoencoder_input, decoded_img, name=\"autoencoder\")\n",
        "autoencoder.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0f77623d9cd5"
      },
      "source": [
        "보시다시피, 모델은 중첩될 수 있습니다. 모델은 하위 모델을 포함할 수 있습니다(모델은 레이어와 유사하므로). 모델 중첩의 일반적인 사용 사례는 *앙상블 기법(ensembling)*입니다. 예를 들어, 모델 세트를 단일 모델로 앙상블하여 예측을 평균화하는 방법은 다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3bb36b630e5d"
      },
      "outputs": [],
      "source": [
        "def get_model():\n",
        "    inputs = keras.Input(shape=(128,))\n",
        "    outputs = layers.Dense(1)(inputs)\n",
        "    return keras.Model(inputs, outputs)\n",
        "\n",
        "\n",
        "model1 = get_model()\n",
        "model2 = get_model()\n",
        "model3 = get_model()\n",
        "\n",
        "inputs = keras.Input(shape=(128,))\n",
        "y1 = model1(inputs)\n",
        "y2 = model2(inputs)\n",
        "y3 = model3(inputs)\n",
        "outputs = layers.average([y1, y2, y3])\n",
        "ensemble_model = keras.Model(inputs=inputs, outputs=outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "447a319b73a6"
      },
      "source": [
        "## 복잡한 그래프 토폴로지 조작\n",
        "\n",
        "### 여러 입력 및 출력 모델\n",
        "\n",
        "함수형 API를 사용하면 다중 입력 및 출력을 쉽게 조작할 수 있습니다. `Sequential` API로는 처리할 수 ​​없습니다.\n",
        "\n",
        "예를 들어 우선 순위별로 사용자 지정 발급 티켓 순위를 매기고 올바른 부서로 라우팅하는 시스템을 구축하는 경우 모델에는 세 가지 입력이 있습니다.\n",
        "\n",
        "- 티켓의 제목 (텍스트 입력)\n",
        "- 티켓의 본문 (텍스트 입력)\n",
        "- 사용자가 추가 한 모든 태그 (범주 입력)\n",
        "\n",
        "이 모델에는 두 가지 출력이 있습니다.\n",
        "\n",
        "- 0과 1 사이의 우선 순위 점수 (스칼라 시그 모이 드 출력)\n",
        "- 티켓을 처리해야하는 부서 (부서 세트에 대한 softmax 출력)\n",
        "\n",
        "함수형 API를 사용하여 이 모델을 몇 줄로 빌드할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "49009e53da63"
      },
      "outputs": [],
      "source": [
        "num_tags = 12  # Number of unique issue tags\n",
        "num_words = 10000  # Size of vocabulary obtained when preprocessing text data\n",
        "num_departments = 4  # Number of departments for predictions\n",
        "\n",
        "title_input = keras.Input(\n",
        "    shape=(None,), name=\"title\"\n",
        ")  # Variable-length sequence of ints\n",
        "body_input = keras.Input(shape=(None,), name=\"body\")  # Variable-length sequence of ints\n",
        "tags_input = keras.Input(\n",
        "    shape=(num_tags,), name=\"tags\"\n",
        ")  # Binary vectors of size `num_tags`\n",
        "\n",
        "# Embed each word in the title into a 64-dimensional vector\n",
        "title_features = layers.Embedding(num_words, 64)(title_input)\n",
        "# Embed each word in the text into a 64-dimensional vector\n",
        "body_features = layers.Embedding(num_words, 64)(body_input)\n",
        "\n",
        "# Reduce sequence of embedded words in the title into a single 128-dimensional vector\n",
        "title_features = layers.LSTM(128)(title_features)\n",
        "# Reduce sequence of embedded words in the body into a single 32-dimensional vector\n",
        "body_features = layers.LSTM(32)(body_features)\n",
        "\n",
        "# Merge all available features into a single large vector via concatenation\n",
        "x = layers.concatenate([title_features, body_features, tags_input])\n",
        "\n",
        "# Stick a logistic regression for priority prediction on top of the features\n",
        "priority_pred = layers.Dense(1, name=\"priority\")(x)\n",
        "# Stick a department classifier on top of the features\n",
        "department_pred = layers.Dense(num_departments, name=\"department\")(x)\n",
        "\n",
        "# Instantiate an end-to-end model predicting both priority and department\n",
        "model = keras.Model(\n",
        "    inputs=[title_input, body_input, tags_input],\n",
        "    outputs=[priority_pred, department_pred],\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ee2735b3eff1"
      },
      "source": [
        "이제 모델을 플롯합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "52c4dc6fd93e"
      },
      "outputs": [],
      "source": [
        "keras.utils.plot_model(model, \"multi_input_and_output_model.png\", show_shapes=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "907c119d04a4"
      },
      "source": [
        "이 모델을 컴파일 할 때 각 출력에 서로 다른 손실을 할당 할 수 있습니다. 각 손실에 다른 가중치를 할당하여 총 교육 손실에 대한 기여도를 조정할 수도 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3e1acef07668"
      },
      "outputs": [],
      "source": [
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss=[\n",
        "        keras.losses.BinaryCrossentropy(from_logits=True),\n",
        "        keras.losses.CategoricalCrossentropy(from_logits=True),\n",
        "    ],\n",
        "    loss_weights=[1.0, 0.2],\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e780b99b133a"
      },
      "source": [
        "출력 레이어의 이름이 다르므로 손실을 다음과 같이 지정할 수도 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "84ce034debf2"
      },
      "outputs": [],
      "source": [
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss={\n",
        "        \"priority\": keras.losses.BinaryCrossentropy(from_logits=True),\n",
        "        \"department\": keras.losses.CategoricalCrossentropy(from_logits=True),\n",
        "    },\n",
        "    loss_weights=[1.0, 0.2],\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "845b20ca3c9d"
      },
      "source": [
        "입력 및 목표치의 NumPy 배열 목록을 전달하여 모델을 훈련합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ae5ff9364b19"
      },
      "outputs": [],
      "source": [
        "# Dummy input data\n",
        "title_data = np.random.randint(num_words, size=(1280, 10))\n",
        "body_data = np.random.randint(num_words, size=(1280, 100))\n",
        "tags_data = np.random.randint(2, size=(1280, num_tags)).astype(\"float32\")\n",
        "\n",
        "# Dummy target data\n",
        "priority_targets = np.random.random(size=(1280, 1))\n",
        "dept_targets = np.random.randint(2, size=(1280, num_departments))\n",
        "\n",
        "model.fit(\n",
        "    {\"title\": title_data, \"body\": body_data, \"tags\": tags_data},\n",
        "    {\"priority\": priority_targets, \"department\": dept_targets},\n",
        "    epochs=2,\n",
        "    batch_size=32,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3c87f1fbe7aa"
      },
      "source": [
        "`Dataset` 객체에 맞춰 호출하면 `([title_data, body_data, tags_data], [priority_targets, dept_targets])`와 같은 목록의 튜플 또는 `({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets})`와 같은 사전의 튜플이 산출됩니다.\n",
        "\n",
        "자세한 설명은 [교육 및 평가](https://www.tensorflow.org/guide/keras/train_and_evaluate/) 안내서를 참조하십시오."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "64ada3f80484"
      },
      "source": [
        "### 장난감 ResNet 모델\n",
        "\n",
        "입력 및 출력이 여러 개인 모델 외에도 함수형 API를 사용하면 비선형 연결 토폴로지를 쉽게 조작할 수 있습니다. 이들은 순차적으로 연결되지 않은 레이어가 있는 모델이며 `Sequential` API가 처리할 수 없는 모델입니다.\n",
        "\n",
        "이에 대한 일반적인 사용 사례는 나머지 연결(residual connections)입니다. 실례를 위해 CIFAR10을 위한 장난감 ResNet 모델을 만들어 봅시다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "bfa8b7503813"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input(shape=(32, 32, 3), name=\"img\")\n",
        "x = layers.Conv2D(32, 3, activation=\"relu\")(inputs)\n",
        "x = layers.Conv2D(64, 3, activation=\"relu\")(x)\n",
        "block_1_output = layers.MaxPooling2D(3)(x)\n",
        "\n",
        "x = layers.Conv2D(64, 3, activation=\"relu\", padding=\"same\")(block_1_output)\n",
        "x = layers.Conv2D(64, 3, activation=\"relu\", padding=\"same\")(x)\n",
        "block_2_output = layers.add([x, block_1_output])\n",
        "\n",
        "x = layers.Conv2D(64, 3, activation=\"relu\", padding=\"same\")(block_2_output)\n",
        "x = layers.Conv2D(64, 3, activation=\"relu\", padding=\"same\")(x)\n",
        "block_3_output = layers.add([x, block_2_output])\n",
        "\n",
        "x = layers.Conv2D(64, 3, activation=\"relu\")(block_3_output)\n",
        "x = layers.GlobalAveragePooling2D()(x)\n",
        "x = layers.Dense(256, activation=\"relu\")(x)\n",
        "x = layers.Dropout(0.5)(x)\n",
        "outputs = layers.Dense(10)(x)\n",
        "\n",
        "model = keras.Model(inputs, outputs, name=\"toy_resnet\")\n",
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "05aefc66c54f"
      },
      "source": [
        "모델을 플롯합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ef7ac19c83be"
      },
      "outputs": [],
      "source": [
        "keras.utils.plot_model(model, \"mini_resnet.png\", show_shapes=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4f0883eae520"
      },
      "source": [
        "이제 모델을 훈련합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4e1c7b530071"
      },
      "outputs": [],
      "source": [
        "(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()\n",
        "\n",
        "x_train = x_train.astype(\"float32\") / 255.0\n",
        "x_test = x_test.astype(\"float32\") / 255.0\n",
        "y_train = keras.utils.to_categorical(y_train, 10)\n",
        "y_test = keras.utils.to_categorical(y_test, 10)\n",
        "\n",
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss=keras.losses.CategoricalCrossentropy(from_logits=True),\n",
        "    metrics=[\"acc\"],\n",
        ")\n",
        "# We restrict the data to the first 1000 samples so as to limit execution time\n",
        "# on Colab. Try to train on the entire dataset until convergence!\n",
        "model.fit(x_train[:1000], y_train[:1000], batch_size=64, epochs=1, validation_split=0.2)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e7f35a9a1061"
      },
      "source": [
        "## 공유 레이어\n",
        "\n",
        "함수형 API의 또 다른 유용한 용도는 *공유 레이어*를 사용하는 모델입니다. 공유 레이어는 같은 모델에서 여러 번 재사용되는 레이어 인스턴스입니다. 레이어 그래프에서 여러 경로에 해당하는 특성을 학습합니다.\n",
        "\n",
        "공유 레이어는 종종 비슷한 공간(예: 유사한 어휘를 제공하는 두 개의 서로 다른 텍스트 조각)의 입력을 인코딩하는 데 사용됩니다. 공유 레이어는  서로 다른 입력 간에 정보를 공유할 수 있으며 적은 데이터에서 모델을 훈련할 수 있습니다. 지정된 단어가 입력 중 하나에 표시되면 공유 레이어를 통해 전달하는 모든 입력을 처리하는 데 도움이 됩니다.\n",
        "\n",
        "함수형 API에서 레이어를 공유하려면, 같은 레이어 인스턴스를 여러 번 호출합니다. 예를 들어, 다음은 두 가지 서로 다른 텍스트 입력 간에 공유되는 `Embedding` 레이어입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4b8e6a4f3e88"
      },
      "outputs": [],
      "source": [
        "# Embedding for 1000 unique words mapped to 128-dimensional vectors\n",
        "shared_embedding = layers.Embedding(1000, 128)\n",
        "\n",
        "# Variable-length sequence of integers\n",
        "text_input_a = keras.Input(shape=(None,), dtype=\"int32\")\n",
        "\n",
        "# Variable-length sequence of integers\n",
        "text_input_b = keras.Input(shape=(None,), dtype=\"int32\")\n",
        "\n",
        "# Reuse the same layer to encode both inputs\n",
        "encoded_input_a = shared_embedding(text_input_a)\n",
        "encoded_input_b = shared_embedding(text_input_b)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b4f193a74581"
      },
      "source": [
        "## 레이어 그래프에서 노드 추출 및 재사용\n",
        "\n",
        "조작하는 레이어의 그래프는 정적 데이터 구조이므로 액세스하여 검사할 수 있습니다. 이를 통해 함수형 모델을 이미지로 플롯할 수 있습니다.\n",
        "\n",
        "즉, 중간 레이어(그래프의 \"노드\")의 활성화에 액세스하여 다른 곳에 재사용할 수 있습니다. 이는 특성 추출과 같은 경우에 매우 유용합니다.\n",
        "\n",
        "예를 봅시다. ImageNet에 가중치가 사전 훈련 된 VGG19 모델입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8bdaa209ccbe"
      },
      "outputs": [],
      "source": [
        "vgg19 = tf.keras.applications.VGG19()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "874ef4b4de49"
      },
      "source": [
        "다음은 그래프 데이터 구조를 쿼리하여 얻은 모델의 중간 활성화입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "391817839937"
      },
      "outputs": [],
      "source": [
        "features_list = [layer.output for layer in vgg19.layers]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e91a9dc2f5b0"
      },
      "source": [
        "다음 특성을 사용하여 중간 레이어 활성화의 값을 반환하는 새로운 feature-extraction 모델을 만듭니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "36a450517b63"
      },
      "outputs": [],
      "source": [
        "feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)\n",
        "\n",
        "img = np.random.random((1, 224, 224, 3)).astype(\"float32\")\n",
        "extracted_features = feat_extraction_model(img)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "f2ac248fe202"
      },
      "source": [
        "이 모델은 [뉴런 스타일 전송](https://keras.io/examples/generative/neural_style_transfer/)과 같은 작업에 특히 유용합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "c894ba891064"
      },
      "source": [
        "## 사용자 정의 레이어를 사용하여 API 확장\n",
        "\n",
        "`tf.keras` 는 다음과 같은 광범위한 내장 레이어를 포함합니다.\n",
        "\n",
        "- 컨볼루션 레이어: `Conv1D`, `Conv2D`, `Conv3D`, `Conv2DTranspose`\n",
        "- 풀링 레이어 : `MaxPooling1D` , `MaxPooling2D` , `MaxPooling3D` , `AveragePooling1D`\n",
        "- RNN 레이어 : `GRU` , `LSTM` , `ConvLSTM2D`\n",
        "- `BatchNormalization` , `Dropout` , `Embedding` 등\n",
        "\n",
        "그러나 필요한 것을 찾지 못하면 자신의 레이어를 만들어 API를 쉽게 확장 할 수 있습니다. 모든 레이어는 `Layer` 클래스를 서브 클래 싱하고 다음을 구현합니다.\n",
        "\n",
        "- 레이어에 의해 수행되는 계산을 지정하는 `call` 메소드.\n",
        "- `build` 레이어의 가중치를 생성하는 방법, (당신이 무게를 만들 수 있기 때문에이 단지 스타일의 규칙입니다 `__init__` 뿐만 아니라,).\n",
        "\n",
        "처음부터 레이어를 만드는 방법에 대한 자세한 내용은 [사용자 정의 레이어 및 모델](https://www.tensorflow.org/guide/keras/custom_layers_and_models) 안내서를 참조하십시오.\n",
        "\n",
        "다음은 `tf.keras.layers.Dense` 의 기본 구현입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1d9faf1f622a"
      },
      "outputs": [],
      "source": [
        "class CustomDense(layers.Layer):\n",
        "    def __init__(self, units=32):\n",
        "        super(CustomDense, self).__init__()\n",
        "        self.units = units\n",
        "\n",
        "    def build(self, input_shape):\n",
        "        self.w = self.add_weight(\n",
        "            shape=(input_shape[-1], self.units),\n",
        "            initializer=\"random_normal\",\n",
        "            trainable=True,\n",
        "        )\n",
        "        self.b = self.add_weight(\n",
        "            shape=(self.units,), initializer=\"random_normal\", trainable=True\n",
        "        )\n",
        "\n",
        "    def call(self, inputs):\n",
        "        return tf.matmul(inputs, self.w) + self.b\n",
        "\n",
        "\n",
        "inputs = keras.Input((4,))\n",
        "outputs = CustomDense(10)(inputs)\n",
        "\n",
        "model = keras.Model(inputs, outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b8933568358c"
      },
      "source": [
        "사용자 정의 레이어에서 직렬화를 지원하려면, 레이어 인스턴스의 constructor 인수를 반환하는 `get_config` 메서드를 정의합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "b22a134918a2"
      },
      "outputs": [],
      "source": [
        "class CustomDense(layers.Layer):\n",
        "    def __init__(self, units=32):\n",
        "        super(CustomDense, self).__init__()\n",
        "        self.units = units\n",
        "\n",
        "    def build(self, input_shape):\n",
        "        self.w = self.add_weight(\n",
        "            shape=(input_shape[-1], self.units),\n",
        "            initializer=\"random_normal\",\n",
        "            trainable=True,\n",
        "        )\n",
        "        self.b = self.add_weight(\n",
        "            shape=(self.units,), initializer=\"random_normal\", trainable=True\n",
        "        )\n",
        "\n",
        "    def call(self, inputs):\n",
        "        return tf.matmul(inputs, self.w) + self.b\n",
        "\n",
        "    def get_config(self):\n",
        "        return {\"units\": self.units}\n",
        "\n",
        "\n",
        "inputs = keras.Input((4,))\n",
        "outputs = CustomDense(10)(inputs)\n",
        "\n",
        "model = keras.Model(inputs, outputs)\n",
        "config = model.get_config()\n",
        "\n",
        "new_model = keras.Model.from_config(config, custom_objects={\"CustomDense\": CustomDense})"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "015abf7d0508"
      },
      "source": [
        "선택적으로, config 사전이 주어진 레이어 인스턴스를 다시 작성할 때 사용되는 클래스 메서드 `from_config(cls, config)`를 구현합니다. `from_config`의 기본 구현은 다음과 같습니다.\n",
        "\n",
        "```python\n",
        "def from_config(cls, config):\n",
        "  return cls(**config)\n",
        "```"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b4ead34e01dd"
      },
      "source": [
        "## 기능적 API를 사용하는 경우\n",
        "\n",
        "언제 Keras 함수형 API를 사용하여 새 모델을 작성하거나 `Model` 클래스를 직접 하위 클래스화해야 할까요? 일반적으로, 함수형 API는 고수준의 쉽고 안전하며, 하위 클래스화되지 않은 모델에서 지원하지 않는 많은 특성을 가지고 있습니다.\n",
        "\n",
        "그러나 모델 하위 클래스화는 레이어의 방향성 비순환 그래프로 쉽게 표현할 수 없는 모델을 빌드할 때 더 큰 유연성을 제공합니다. 예를 들어, 함수형 API로 Tree-RNN을 구현할 수 없었으며 `Model`을 직접 하위 클래스화해야 했습니다.\n",
        "\n",
        "함수형 API와 모델 하위 클래스화의 차이점에 대한 자세한 내용은 [TensorFlow 2.0의 기호 및 명령형 API란 무엇입니까?](https://blog.tensorflow.org/2019/01/what-are-symbolic-and-imperative-apis.html)를 참조하세요.\n",
        "\n",
        "### 기능적 API 강점 :\n",
        "\n",
        "다음 특성은 Sequential 모델(데이터 구조이기도 함)에도 적용되지만, 하위 클래스화된 모델(데이터 구조가 아닌 파이썬 바이트 코드)에는 적용되지 않습니다.\n",
        "\n",
        "#### 덜 복잡하다\n",
        "\n",
        "`super(MyClass, self).__init__(...)` , `def call(self, ...):` 등이 없습니다.\n",
        "\n",
        "비교:\n",
        "\n",
        "```python\n",
        "inputs = keras.Input(shape=(32,))\n",
        "x = layers.Dense(64, activation='relu')(inputs)\n",
        "outputs = layers.Dense(10)(x)\n",
        "mlp = keras.Model(inputs, outputs)\n",
        "```\n",
        "\n",
        "하위 클래스화된 버전:\n",
        "\n",
        "```python\n",
        "class MLP(keras.Model):\n",
        "\n",
        "  def __init__(self, **kwargs):\n",
        "    super(MLP, self).__init__(**kwargs)\n",
        "    self.dense_1 = layers.Dense(64, activation='relu')\n",
        "    self.dense_2 = layers.Dense(10)\n",
        "\n",
        "  def call(self, inputs):\n",
        "    x = self.dense_1(inputs)\n",
        "    return self.dense_2(x)\n",
        "\n",
        "# Instantiate the model.\n",
        "mlp = MLP()\n",
        "# Necessary to create the model's state.\n",
        "# The model doesn't have a state until it's called at least once.\n",
        "_ = mlp(tf.zeros((1, 32)))\n",
        "```\n",
        "\n",
        "#### 연결 그래프를 정의하면서 모델을 검증한다\n",
        "\n",
        "함수형 API에서는 입력 사양(형상 및 dtype)이 사전에 작성됩니다 (`Input` 사용). 레이어를 호출할 때마다 레이어는 전달된 사양이 해당 가정과 일치하는지 확인하고 그렇지 않은 경우 유용한 오류 메시지를 발생시킵니다.\n",
        "\n",
        "이를 통해 함수형 API로 빌드할 수 있는 모든 모델이 실행됩니다. 수렴 관련 디버깅 이외의 모든 디버깅은 실행 중이 아니라 모델 구성 중에 정적으로 발생합니다. 이것은 컴파일러에서의 유형 검사와 유사합니다.\n",
        "\n",
        "#### 함수형 모델은 플롯 및 검사 가능하다\n",
        "\n",
        "모델을 그래프로 플롯하고, 이 그래프에서 중간 노드에 쉽게 액세스할 수 있습니다. 예를 들어, 중간 레이어의 활성화를 추출하여 재사용합니다(이전 예제에서 볼 수 있음).\n",
        "\n",
        "```python\n",
        "features_list = [layer.output for layer in vgg19.layers]\n",
        "feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)\n",
        "```\n",
        "\n",
        "#### 함수형 모델은 직렬화 또는 복제 가능하다\n",
        "\n",
        "함수형 모델은 코드가 아닌 데이터 구조이기 때문에 안전하게 직렬화할 수 있으며 단일 코드로 저장하여 원본 코드에 액세스하지 않고도 정확히 같은 모델을 다시 만들 수 있습니다. [직렬화 및 저장 안내서](https://www.tensorflow.org/guide/keras/save_and_serialize/)를 참조하세요.\n",
        "\n",
        "하위 클래스화된 모델을 직렬화하려면 구현자가 모델 레벨에서 `get_config()` 및 `from_config()` 메서드를 지정해야 합니다.\n",
        "\n",
        "### 기능적 API 약점 :\n",
        "\n",
        "#### 동적 아키텍처를 지원하지 않는다\n",
        "\n",
        "함수형 API는 모델을 레이어의 DAG로 취급합니다. 이는 대부분의 딥 러닝 아키텍처에 해당하지만, 전부는 아닙니다. 예를 들어, 재귀 네트워크 또는 트리 RNN은 이 가정을 따르지 않으며 함수형 API로 구현할 수 없습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "72992d4ed462"
      },
      "source": [
        "## 믹스 앤 매치 API 스타일\n",
        "\n",
        "함수형 API 또는 Model 하위 클래스화 중에서 선택하는 것은 하나의 모델 범주로 제한하는 이진 결정이 아닙니다. `tf.keras` API의 모든 모델은 `Sequential` 모델, 함수형 모델 또는 처음부터 작성한 하위 클래스화된 모델과 관계없이 서로 상호 작용할 수 있습니다.\n",
        "\n",
        "하위 클래스화된 모델 또는 레이어의 일부로 함수형 모델 또는 `Sequential` 모델을 항상 사용할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3c6221508766"
      },
      "outputs": [],
      "source": [
        "units = 32\n",
        "timesteps = 10\n",
        "input_dim = 5\n",
        "\n",
        "# Define a Functional model\n",
        "inputs = keras.Input((None, units))\n",
        "x = layers.GlobalAveragePooling1D()(inputs)\n",
        "outputs = layers.Dense(1)(x)\n",
        "model = keras.Model(inputs, outputs)\n",
        "\n",
        "\n",
        "class CustomRNN(layers.Layer):\n",
        "    def __init__(self):\n",
        "        super(CustomRNN, self).__init__()\n",
        "        self.units = units\n",
        "        self.projection_1 = layers.Dense(units=units, activation=\"tanh\")\n",
        "        self.projection_2 = layers.Dense(units=units, activation=\"tanh\")\n",
        "        # Our previously-defined Functional model\n",
        "        self.classifier = model\n",
        "\n",
        "    def call(self, inputs):\n",
        "        outputs = []\n",
        "        state = tf.zeros(shape=(inputs.shape[0], self.units))\n",
        "        for t in range(inputs.shape[1]):\n",
        "            x = inputs[:, t, :]\n",
        "            h = self.projection_1(x)\n",
        "            y = h + self.projection_2(state)\n",
        "            state = y\n",
        "            outputs.append(y)\n",
        "        features = tf.stack(outputs, axis=1)\n",
        "        print(features.shape)\n",
        "        return self.classifier(features)\n",
        "\n",
        "\n",
        "rnn_model = CustomRNN()\n",
        "_ = rnn_model(tf.zeros((1, timesteps, input_dim)))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "41f42eb2a9c0"
      },
      "source": [
        "다음 패턴 중 하나를 따르는 `call` 메서드를 구현하는 한 함수형 API에서 하위 클래스화된 레이어 또는 모델을 사용할 수 있습니다.\n",
        "\n",
        "- `call(self, inputs, **kwargs)` - `inputs`는 텐서 또는 텐서의 중첩 구조(예: 텐서의 목록)이며, `**kwargs`는 비 텐서 인수(non-inputs)입니다.\n",
        "- `call(self, inputs, training=None, **kwargs)` - `training`은 레이어가 훈련 모드 및 추론 모드에서 동작해야 하는지 여부를 나타내는 boolean입니다.\n",
        "- `call(self, inputs, mask=None, **kwargs)` - `mask`는 boolean 마스크 텐서(예를 들어, RNN에 유용)입니다.\n",
        "- `call(self, inputs, training=None, mask=None, **kwargs)` -물론 마스킹과 교육 별 동작을 동시에 가질 수 있습니다.\n",
        "\n",
        "또한, 사용자 정의 Layer 또는 모델에서 `get_config` 메서드를 구현하면 작성하는 함수형 모델은 계속 직렬화 가능하고 복제 가능합니다.\n",
        "\n",
        "다음은 함수형 모델에 사용되는 처음부터 작성된 사용자 정의 RNN의 간단한 예입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3deb90222d05"
      },
      "outputs": [],
      "source": [
        "units = 32\n",
        "timesteps = 10\n",
        "input_dim = 5\n",
        "batch_size = 16\n",
        "\n",
        "\n",
        "class CustomRNN(layers.Layer):\n",
        "    def __init__(self):\n",
        "        super(CustomRNN, self).__init__()\n",
        "        self.units = units\n",
        "        self.projection_1 = layers.Dense(units=units, activation=\"tanh\")\n",
        "        self.projection_2 = layers.Dense(units=units, activation=\"tanh\")\n",
        "        self.classifier = layers.Dense(1)\n",
        "\n",
        "    def call(self, inputs):\n",
        "        outputs = []\n",
        "        state = tf.zeros(shape=(inputs.shape[0], self.units))\n",
        "        for t in range(inputs.shape[1]):\n",
        "            x = inputs[:, t, :]\n",
        "            h = self.projection_1(x)\n",
        "            y = h + self.projection_2(state)\n",
        "            state = y\n",
        "            outputs.append(y)\n",
        "        features = tf.stack(outputs, axis=1)\n",
        "        return self.classifier(features)\n",
        "\n",
        "\n",
        "# Note that you specify a static batch size for the inputs with the `batch_shape`\n",
        "# arg, because the inner computation of `CustomRNN` requires a static batch size\n",
        "# (when you create the `state` zeros tensor).\n",
        "inputs = keras.Input(batch_shape=(batch_size, timesteps, input_dim))\n",
        "x = layers.Conv1D(32, 3)(inputs)\n",
        "outputs = CustomRNN()(x)\n",
        "\n",
        "model = keras.Model(inputs, outputs)\n",
        "\n",
        "rnn_model = CustomRNN()\n",
        "_ = rnn_model(tf.zeros((1, 10, 5)))"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "functional.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
